//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   PACKAGE   : dcf
//   FILE      : Service.java
//   CLASS     : Service
//   DATE      : 2006-04-01
//   
//   DESCRIPTION
//   The Service class represents a concrete service provided by a component within the DCF framework. It 
//   manages a collection of remote endpoints (where messages can be sent to) as well as provides operations
//   for sending messages to a specific endpoint or to all endpoints (akin to a broadcast).
//
//   
//   
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   Copyright (c) 2006 by Hal Smith
//
//   Permission is hereby granted, free of charge, to any person obtaining a copy of this source code 
//   (the "Code")to deal with the Code without restriction, including without limitation the rights to use, copy,
//   modify, merge, publish and distribute the Code, and to permit persons to whom the Code is furnished to do 
//   so, subject to the following conditions:
//
//   The above copyright notice and this permission notice shall be included in all copies or substantial 
//   portions of the Software.
//   
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   VERSION HISTORY
//
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

package dcf;

import java.util.Vector;

class Service implements IService {
	
	// The name and type of the service.
	//
	private String            serviceName;
	private ServiceType       serviceType;
	
	
	// The outgoing behavior which will used to actually send a message to remote endpoints.
	//
	private IOutgoingBehavior outgoingBehavior;
	
	
	// The endpoint of at which the service resides. This is used as a return address on a message.
	//
	private Endpoint         localEndpoint;
	
	
	// A list of the remote endpoints connected to this service. Note that if the service type is SUBSCRIBER
	// then the list may have only one remote endpoint in it. PUBLISHER services may have many remote 
	// endpoints.
	//
	private Vector<Endpoint> remoteEndpoints;
	
	
	// Constructor.
	//
	// This operation initializes the service. If the service name, type or local endpoint parameters are 
	// null, the operation will log an error and throw an initialization exception. There are no other 
	// exceptions thrown by this operation.
	//
    public Service(String      initServiceName, 
    		       ServiceType initServiceType,
    		       Endpoint    initLocalEndpoint) throws InitializationException {
    	
    	
    	// Ensure that none of the supplied values were null. If any were, log the appropriate message and throw
    	// an InitializationException. Otherwise, save the information locally and initialize the rest of the
    	// structure.
    	//
    	if (initServiceName   == null || 
    	    initServiceType   == null ||
    	    initLocalEndpoint == null) {
    		
			System.out.println("Service.Service(String, IService.ServiceType, Endpoint):");
			System.out.println("   One or more of the supplied parameters was null.");
			System.out.println("   Service Name null?   " + (initServiceName   == null));
			System.out.println("   Service type null?   " + (initServiceType   == null));
			System.out.println("   Local endpoint null? " + (initLocalEndpoint == null));
			
			throw new InitializationException();
    		    		
    	} else {
    		
        	serviceName      = initServiceName;
        	serviceType      = initServiceType;   
        	localEndpoint    = initLocalEndpoint;
        	
        	remoteEndpoints = new Vector<Endpoint>();
    		
    	}
    
    } // constructor
    
    
    // This operation returns the name of the service. There are no exceptions thrown by this operation.
    //
    public String getServiceName() {
    	
    	return serviceName;
    
    } // getServiceName()
    
    
    // This operation returns the type of the service. There are no exceptions thrown by this operation.
    //
    public ServiceType getServiceType() {

    	return serviceType;
    
    } // getServiceType

    
	// This operation returns the number of endpoints currently bound. There are no exceptions thrown by this
    // operation.
	//
	public int getEndpointCount() {
	
		return remoteEndpoints.size();
	
	} // getEndpointCount()
	 
	
	// This operation returns true if there is at least one remote endpoint. There are no exceptions thrown
	// by this operation.
	//
	public boolean isBound() {

		return (remoteEndpoints.size() == 0 ? true : false);
	
	} // isBound()

	
	// This operation adds the endpoint to the list of remote endpoints. If the service type is SUBSCRIBER
	// and a remote endpoint currently is associated, the current publisher will be replaced with the
	// specified publisher. There are no exceptions thrown by this operation. 
	//
	public void addRemoteEndpoint(Endpoint newEndpoint) {
		
		// TODO: Need to deal with the case where a subscriber attempts to have multiple publishers
		//       attached.
		//
        remoteEndpoints.add(newEndpoint);
    
	} // addRemoteEndpoint()
    
 
	// This operation returns the collection of remote endpoints currently known. There are no exceptions
	// thrown by this operation.
	//
	public Vector<Endpoint> getRemoteEndpoints() {
    	
		// Since we do not want users to modify the collection of endpoints for this service without
		// going through this interface, we create an identical collection to be returned.
		//
		return (Vector<Endpoint>)remoteEndpoints.clone();
		    
    } // getRemoteEndpoints()
    
    
	// This operation removes the specified endpoint from the list of remote endpoints. If the endpoint is not
	// in the list, no action is taken but an error is logged. There are no exceptions thrown from this
	// operation.
	//
    public void removeRemoteEndpoint(Endpoint endpoint) {

    	// Attempt to remove the endpoint from the list of remote endpoints. If the specified endpoint is not
    	// on the list or is null, report an appropriate error.
    	//
    	if (endpoint != null) {
    		
    		if (!remoteEndpoints.remove(endpoint)) {
    			
    			System.out.println("Service.removeRemoteEndpoint():");
    			System.out.println("   Endpoint specified to remove was not in list. Ignoring request.");
    			System.out.println("   Endpoint host: " + endpoint.getHost());
    			System.out.println("   Endpoint port: " + endpoint.getPort());
    			System.out.println("   Service name: " + getServiceName());
    			System.out.println("   Service host: " + localEndpoint.getHost());
    			System.out.println("   Service port: " + localEndpoint.getPort());
    			
    		} 
    		
    	} else {
    		
			System.out.println("Service.removeRemoteEndpoint():");
			System.out.println("   Endpoint specified to remove was null. Ignoring request.");
			System.out.println("   Service name: " + getServiceName());
			System.out.println("   Service host: " + localEndpoint.getHost());
			System.out.println("   Service port: " + localEndpoint.getPort());
    		
    	} 
    	    	
    } // removeRemoteEndpoint()
    
    
    // This operation removes all enpoints from the list of remote endpoints. There are no exceptions thrown
    // by this operation.
    //
    public void clearRemoteEndpoints() {
    
    	remoteEndpoints.clear();
    
    } // clearRemoteEndpoints()
    
    
    // This operation updates the service to use the specified outgoing behavior. If the specified outgoing
    // behvaior is null, the operation ignores the request and logs an error. There are no exceptions thrown
    // from this operation.
    //
    public void setOutgoingBehavior(IOutgoingBehavior newOutgoingBehavior) {
    	
    	if (newOutgoingBehavior != null) {
    		
    		outgoingBehavior = newOutgoingBehavior;
    	
    	} else {
    		
			System.out.println("Service.setOutgoingBehavior():");
			System.out.println("   Attempted to set outgoing behavior to null. Ignoring request.");
			System.out.println("   Service name: " + getServiceName());
			System.out.println("   Service host: " + localEndpoint.getHost());
			System.out.println("   Service port: " + localEndpoint.getPort());
   		
    	}
    	
    } // setOutgoingBehavior()
    
    
    // This operation updates the service to use the specified endpoint as its local endpoint. If the endpoint
    // specified is null, the operation ignores the request and logs an error. There are no exceptions thrown
    // by this operation.
    //
    public void setLocalEndpoint(Endpoint newEndpoint) {
    	
    	if (newEndpoint != null) {
    		
        	localEndpoint = newEndpoint;
    		
    	} else {
    		
			System.out.println("Service.setLocalEndpoint():");
			System.out.println("   Attempted to set local endpoint to null. Ignoring request.");
			System.out.println("   Service name: " + getServiceName());
			System.out.println("   Service host: " + localEndpoint.getHost());
			System.out.println("   Service port: " + localEndpoint.getPort());
   		
    	}
    
    }
    
    
    // This operation sends a message to all remote endpoints. If the message is sent successfully, the
    // operation returns "true". If an error occurs while sending the message, the operation returns false
    // and an error is logged. Note that because "false" is returned does not mean that the message was 
    // not sent, only that an error occured. There are no excpetions thrown by this operation.
    //
    public boolean send(Message message) {
    	
    	boolean result = true;
    	
    	
    	// If there is a specified outgoing behavior, try to send the message to each remote endpoint. 
    	// Otherwise, log an error and return false.
    	//
    	if (outgoingBehavior != null) {

    		try {
    			
    			// Set the return address to the local endpoint and send the message to each remote endpoint.
    			//
	    		message.setSenderEndpoint(localEndpoint);
	    	
		    	if (remoteEndpoints.size() > 0) {

		    		for (Endpoint e : remoteEndpoints) {
			    	   outgoingBehavior.sendTo(e, message);
			    	}
		    	
		    	}
    		
    		} catch (Exception e) {
    			
    			// Log the exception and return false to the caller to indicate an error occured.
    			//
    			System.out.println("Service.send(Message):");
    			System.out.println("   Unexpected exception occurred while sendng the message: " 
    					           + e.toString());
    			System.out.println("   Service name: " + getServiceName());
    			System.out.println("   Service host: " + localEndpoint.getHost());
    			System.out.println("   Service port: " + localEndpoint.getPort());    			
    			
    			result = false;
    		
    		} // try-catch
	    	
    	} else {
    		
			System.out.println("Service.send(Message):");
			System.out.println("   There is currently no associated outgoing behavior. Cannot send the message.");
			System.out.println("   Service name: " + getServiceName());
			System.out.println("   Service host: " + localEndpoint.getHost());
			System.out.println("   Service port: " + localEndpoint.getPort());
    		
			result = false;

    	} // if (outgoingBehavior != null) ...
    	
    	
    	return result;
    	
    	
    } // send()
    

    // This operation sends a message to the specified endpoint. If the message is sent successfully, the
    // operation returns "true". If an error occurs while sending the message, the operation returns false
    // and an error is logged. Note that because "false" is returned does not mean that the message was 
    // not sent, only that an error occured. There are no excpetions thrown by this operation.
    //
    public boolean sendTo(Endpoint remoteEndpoint, Message message) {
    	
	   	boolean result = true;
				
		// If there is a specified outgoing behavior, try to send the message. Otherwise, log an error and 
	   	// return false.
		//
		if (outgoingBehavior != null) {
	
			try {
				
				// Set the return address to the local endpoint and send the message to each remote endpoint.
				//
		    	message.setSenderEndpoint(localEndpoint);
		    	
		    	outgoingBehavior.sendTo(remoteEndpoint, message);
			
			} catch (Exception e) {
				
				// Log the exception and return false to the caller to indicate an error occured.
				//
				System.out.println("Service.send(Message):");
				System.out.println("   Unexpected exception occurred while sendng the message: " 
						           + e.toString());
				System.out.println("   Service name: " + getServiceName());
				System.out.println("   Service host: " + localEndpoint.getHost());
				System.out.println("   Service port: " + localEndpoint.getPort());    			
				
				result = false;
			
			} // try-catch
	    	
		} else {
			
			System.out.println("Service.send(Message):");
			System.out.println("   There is currently no associated outgoing behavior. Cannot send the message.");
			System.out.println("   Service name: " + getServiceName());
			System.out.println("   Service host: " + localEndpoint.getHost());
			System.out.println("   Service port: " + localEndpoint.getPort());
			
			result = false;
			
		} // if (outgoingBehavior != null) ...
		
		
		return result;
	    
		
    } // sendTo()
    
    
} // Service
